from tetra import *
from copy import *

#typeconvert = {
#    'int' : [('float', ftoi), ],
#    'float' : [],
#}

class Type:
    def __init__(self, name, args):
        self.name = name
        self.args = args
        for a in self.args:
            assert(a.__class__ == Var)

    def __str__(self):
        return self.name

    def __ne__(self, other):
        return not self == other

    def __eq__(self, other):
        if type(self) != type(other):
            return False;
        for i, a in enumerate(self.args):
            if a.type != other.args[i].type:
                return False
            if a.name != other.args[i].name:
                return False
        return True

    def tostr(self): 
        return 'Type: %s, %s' % (self.name, str(map(lambda x : x.tostr(), self.args)))

class Label:
    def __init__(self, name):
        self.name = name

    def __str__(self):
        return self.name

    def getView(self):
        return self.name

class Const:
    def __init__(self, value, type):
        self.value = value
        self.type = type

    def __str__(self):
        return str(self.value)

    def getType(self):
        return self.type

    def getView(self):
        return str(self.value)

class Var:
    def __init__(self, name, type, internalId = None):
        self.name = name
        self.type = type
        self.id = internalId
        self.subvars = []

    def __str__(self):
        return self.id

    def getType(self):
        return self.type

    def getView(self):
        return self.name

    def tostr(self):
        if type(self.type) != type(''):
            return self.name + ', ' + self.type.tostr()
        else:
            return self.name + ', ' + str(self.type)

class Func:
    def __init__(self, name, labelName, args, ret):
        self.name = name
        self.labelName = labelName
        self.args = args
        self.ret = ret

    def __str__(self):
        return self.labelName

    def getView(self):
        return self.name

class VarList:
    def __init__(self):
        self._list = {}
        self._subvars = {}

    def getUniqueID(self):
        maxid = 0
        if self._list:
            maxid = max(map(int, map(lambda x : x[1:], self._list.keys())))
        return 'v%s' % str(maxid + 1)

    def getUniqueName(self):
        return '__temp_id_%s' % self.getUniqueID()

    def getTempVar(self, type):
        id = self.getUniqueID()
        name = self.getUniqueName()
        self._list[id] = type, name
        return Var(name, type, id)

    def addVar(self, varType, varName):
        for k, (vt, vn) in self._list.items():
            if (varName == vn):
                raise RuntimeError('Duplicated variable name: %s' % vn)

        id = self.getUniqueID()
#        if id == 'v22':
#            rafd

        self._list[id] = varType, varName
        self._subvars[id] = []
        return Var(varName, varType, id)

    def removeVar(self, varName):
        for k, (vt, vn) in self._list.items():
            if (varName == vn):
                self._list[k] = '', ''
                return

    def updateSubvars(self, var):
        for k, v in self._list.items():
            t, n = v
            if n == var.getView():
                self._subvars[k] = var.subvars
                return
        assert(False)

    def getVar(self, varName):
        for k, v in self._list.items():
            t, n = v
            if n == varName:
                var = Var(n, t, k)
                var.subvars = self._subvars[k]
                return var
        raise RuntimeError("Can't find variable: %s" % varName)

    def setType(self, varName, newType):
        for k, v in self._list.items():
            t, n = v
            if n == varName:
                self._list[k] = newType, n
                return
        assert(False)

class Tetragen:
    def __init__(self):
        self._code = []

        self._flist = {}
        self._vlist = VarList()
        self._llist = []
        self._line  = None
        self._tlist = {'int' : 'int', 'float' : 'float', 'array' : None}

    def _tetr(self, op, a1, a2, res):
        t = Tetrada(op, a1, a2, res)
        t.setLine(self._line)
        return t

    def _getTempTypeName(self):
        i = 0;
        while ('__temp_%s' % i) in self._tlist.keys():
            i = i + 1
        return '__temp_%s' % i

    def _createFunc(self, fname, args, retType):
        for id, (name, a, ret) in self._flist.items():
            if fname == name and len(a) == len(args):
                gettypef = lambda x : x.arg1.getType()
                if map(gettypef, a) == map(gettypef, args):
                    raise RuntimeError('Duplicated function: %s' % fname)
        ret = None
        if retType != 'void':
            ret = self._vlist.getTempVar(retType)
        maxid = 0
        if self._flist:
            maxid = max(map(int, map(lambda x : x[1:], self._flist.keys())))
        id = 'f%s' % str(maxid + 1)
        self._flist[id] = fname, args, ret
        return Func(fname, id, args, ret)

    def _getFunc(self, fname, inargs):
        intypes = map(lambda x : x[1].getType() , inargs)
        for id, (name, args, ret) in self._flist.items():
            if fname == name and len(args) == len(inargs):
                argtypes = map(lambda x : x.arg1.getType(), args)
                if intypes == argtypes:
                    return Func(name, id, args, ret)
        raise RuntimeError('Cant find function: %s with argument types: %s' % (fname, intypes))

    def _getTempLabel(self):
        maxid = 0
        if self._llist:
            intlist = []
            for l in self._llist:
                try:
                    intval = int(l[1:])
                    intlist.append(intval)
                except:
                    pass
            if intlist:
                maxid = max(intlist)
        name = 'l%s' % str(maxid + 1)
        self._llist.append(name)
        return Label(name)

    def setLine(self, line):
        self._line = line

    def unarOperatorCall(self, name, arg):
        return self.funcCall('operator%s' % name.strip(), [ arg ])

    def operatorCall(self, name, arg1, arg2):
        stdoperators = {
            ('operator+', 'int', 'int')     : self.sum,
            ('operator+', 'float', 'float') : self.sum,
            ('operator-', 'int', 'int')     : self.sub,
            ('operator-', 'float', 'float') : self.sub,
            ('operator*', 'int', 'int')     : self.mul,
            ('operator*', 'float', 'float') : self.mul,
            ('operator/', 'int', 'int')     : self.div,
            ('operator/', 'float', 'float') : self.div,
            ('operator==','int', 'int')     : self.eq ,
            ('operator==','float', 'float') : self.eq ,
            ('operator>', 'int',  'int')    : self.more,
            ('operator>', 'float', 'float') : self.more,
            }
        opdef = 'operator%s' % name.strip(), arg1[1].getType(), arg2[1].getType()
        if opdef in stdoperators.keys():
            return stdoperators[opdef](arg1, arg2)
        return self.funcCall(opdef[0], [arg1, arg2])

    def sum(self, arg1, arg2):
        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        tempVar = self._vlist.getTempVar(a1.getType())
        return arg1[0] + arg2[0] + [self._tetr('+', a1, a2, tempVar)], tempVar

    def sub(self, arg1, arg2):
        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        tempVar = self._vlist.getTempVar(a1.getType())
        return arg1[0] + arg2[0] + [self._tetr('-', a1, a2, tempVar)], tempVar

    def mul(self, arg1, arg2):
        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        tempVar = self._vlist.getTempVar(a1.getType())
        return arg1[0] + arg2[0] + [self._tetr('*', a1, a2, tempVar)], tempVar

    def div(self, arg1, arg2):
        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        tempVar = self._vlist.getTempVar(a1.getType())
        return arg1[0] + arg2[0] + [self._tetr('/', a1, a2, tempVar)], tempVar

    def getVar(self, varName):
        return [], self._vlist.getVar(varName)

    def getConst(self, value, type):
        return [], Const(value, type)

    def addVar(self, varType, varName, typen = None):
        varType = str(varType)
        if varType.startswith('array'):
            ar, num = varType.split(':')[:2]
            subtype = ':'.join(varType.split(':')[2:])
            num = int(num)
            assert(ar == 'array')
            expr = self.addVar(subtype, varName);
            resexpr = self.setArraySize(expr, num)
            return resexpr

        if not typen:
            if not varType in self._tlist.keys():
                raise RuntimeError('Unknown type: %s' % varType)
            typen = self._tlist[varType]
        var = self._vlist.addVar(typen, varName)
        code = [self._tetr('VARDECL', var, None, None)]
        if type(typen) != type(''):
            for i, v in enumerate(var.getType().args):
                subvarname = '__%s__%s' % (i, var.getView())
                subvarcode, subvar = self.addVar(v.getType(), subvarname)
                var.subvars.append(subvar)
                code = code + subvarcode
        self._vlist.updateSubvars(var)
        return code, var

    def assignValue(self, assignTo, assignFrom):
        if (assignTo.getType() != assignFrom[1].getType()):
            raise RuntimeError('Cant assign types %s to %s' % (assignFrom[1].getType(), assignTo.getType())) 
        code = assignFrom[0] + [self._tetr(':=', assignFrom[1], None, assignTo)]
        if type(assignTo.getType()) != type(''):
            for i, v in enumerate(assignTo.getType().args):
                subvarnameTo   = '__%s__%s' % (i, assignTo.getView())
                subvarnameFrom = '__%s__%s' % (i, assignFrom[1].getView())
                try:
                    _, subvarTo = self.getVar(subvarnameTo)
                except:
                    subvarTo = assignTo.subvars[i]
                subfarFromExpr = self.getVar(subvarnameFrom)
                code = code + self.assignValue(subvarTo, subfarFromExpr)
        return code

    def closeBlock(self, list):        
#        res = []
        for l in list:
            if l.operation == 'VARDECL':
                self._vlist.removeVar(l.arg1.getView())
#            else:
#                res.append(l)
        return list

    def printValue(self, expr):
        return expr[0] + [ self._tetr('PRINT', expr[1], None, None) ]

    def readValue(self, var):
        return [ self._tetr('READ', None, None, var[1]) ]

    def ifSt(self, cond, code):
        lendif = self._getTempLabel()
        return cond[0] + [
                self._tetr('BZL', lendif, cond[1], None)
            ] + code + [
                self._tetr('DEFL', lendif, None, None)
            ]

    def whileSt(self, cond, code):
        lbeginwhile = self._getTempLabel()
        lendwhile = self._getTempLabel()
        return [ self._tetr('DEFL', lbeginwhile, None, None),
            ] + cond[0] + [
                self._tetr('BZL',  lendwhile, cond[1], None),
            ] + code + [
                self._tetr('BRL',  lbeginwhile, None, None),
                self._tetr('DEFL', lendwhile, None, None),
            ]

    def more(self, arg1, arg2):
        ltrue = self._getTempLabel()
        lend  = self._getTempLabel()

        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        resvar  = self._vlist.getTempVar('int')
        tempvar = self._vlist.getTempVar(a1.getType())
        code = [
            self._tetr('-', a1, a2, tempvar),
            self._tetr('BPL', ltrue, tempvar, None),
        ] + self.assignValue(resvar, self.getConst(0, 'int')) + [
            self._tetr('BRL', lend, None, None),
            self._tetr('DEFL', ltrue, None, None),
        ] + self.assignValue(resvar, self.getConst(1, 'int')) + [ 
            self._tetr('DEFL', lend, None, None), 
            ]
        return arg1[0] + arg2[0] + code, resvar

    def eq(self, arg1, arg2):
        ltrue = self._getTempLabel()
        lend  = self._getTempLabel()

        a1, a2 = arg1[1], arg2[1]
#        a1, a2 = self._convertVars(arg1[1], arg2[1])
        resvar  = self._vlist.getTempVar('int')
        tempvar = self._vlist.getTempVar(a1.getType())
        code = [
            self._tetr('-', a1, a2, tempvar),
            self._tetr('BZL', ltrue, tempvar, None),
            ] +  self.assignValue(resvar, self.getConst(0, 'int')) + [
            self._tetr('BRL', lend, None, None),
            self._tetr('DEFL', ltrue, None, None),
            ] + self.assignValue(resvar, self.getConst(1, 'int')) + [
            self._tetr('DEFL', lend, None, None),
            ]
        return arg1[0] + arg2[0] + code, resvar

    def defLabel(self, labelName):
        labelName = 'user_label_%s' % labelName
        assert(not labelName in self._llist)
        self._llist.append(labelName)
        return [ self._tetr('DEFL', Label(labelName), None, None) ]

    def gotoLabel(self, labelName):
        labelName = 'user_label_%s' % labelName
        assert(labelName in self._llist)
        return [ self._tetr('BRL', Label(labelName), None, None) ]

    def forSt(self, init, cond, iter, code):
        wcode = code + iter
        return init + self.whileSt(cond, wcode)

    def operatorDef(self, retType, name, args, body):
        return self.funcDef(retType, 'operator%s' % name.strip(), args, body)

    def funcDef(self, retType, name, args, body):
        lend = self._getTempLabel()

        fargs = filter(lambda x : not x.arg1.getView().startswith('__'), args)

        func = self._createFunc(name, fargs, retType)
        flabel = Label(func.labelName)
        retdecl = []
        if func.ret:
            retdecl = [ self._tetr('VARDECL', func.ret, None, None) ]
        code = [ self._tetr('BRL', lend, None, None) ] +\
                 args + retdecl +\
               [ self._tetr('DEFL', flabel, None, None) ] +\
                 body +\
               [ self._tetr('RET', None, None, None), 
                 self._tetr('DEFL', lend, None, None) ]
        
        for l in args:
            self._vlist.removeVar(l.arg1.getView())

        added = True
        while added:
            added = False
            for i in range(len(code)):
                if code[i].operation == 'RETVAL':
                    begin = code[:i]
                    end   = code[i+1:]
                    retval = code[i]
                    code = begin + self.assignValue(func.ret, retval.arg1) + end
                    added = True
                    break
        return code

    def funcReturn(self, expr):
        return [ self._tetr('RETVAL', expr, None, None), self._tetr('RET', None, None, None) ]

    def a2i(self, arg):
        tempVar = self._vlist.getTempVar('int')
        return arg[0] + [ self._tetr('A2I', arg[1], None, tempVar) ], tempVar

    def i2a(self, arg):
        tempVar = self._vlist.getTempVar('float')
        return arg[0] + [ self._tetr('I2A', arg[1], None, tempVar) ], tempVar

    def funcCall(self, name, args): #, res = None):
        if name == 'int' and len(args) == 1 and args[0][1].getType() == 'float':
            return self.a2i(args[0])
        if name == 'float' and len(args) == 1 and args[0][1].getType() == 'int':
            return self.i2a(args[0])

        func = self._getFunc(name, args)
        code = []
        for i in range(len(func.args)):
            code = code + self.assignValue(func.args[i].arg1, args[i])
        code = code + [ self._tetr('CALL', func, None, None) ]
        
#        retvar = None
#        if func.ret:
#            retvar = self._vlist.getTempVar(func.ret.getType())
#            code = code + [ self._tetr(':=', func.ret, None, retvar) ]
#        return code, retvar
        return code, func.ret

    def markLine(self, tetrads, line):
        if tetrads:
            tetrads[-1].setLine(line)
        return tetrads

    def defStruct(self, name, body):
        assert(not name in self._tlist.keys())
        for b in body:
            assert(b.operation == 'VARDECL')
            self._vlist.removeVar(b.arg1.getView())
        args = map(lambda x : x.arg1, body)
        type = Type(name, args)
        self._tlist[name] = type
        return []

    def getSubVar(self, var, subname):
        for i, v in enumerate(var[1].getType().args):
            if v.getView() == subname:
                subvarname = '__%s__%s' % (i, var[1].getView())
                subvarExpr = self.getVar(subvarname)
                assert (subvarExpr[1].getType() == v.getType())
                return var[0] + subvarExpr[0], subvarExpr[1]
        assert(False)

    def getSubArray(self, var, expr, tovar = None):
        code = var[0]
        if type(var[1].getType()) == type(''):
            ar, num = var[1].getType().split(':')[:2]
            subtype = ':'.join(var[1].getType().split(':')[2:])
            num = int(num)
            assert(ar == 'array')
            condExpr = [], None
            ifst = []
#            condExpr = self.more(expr, self.getConst(num - 1, 'int'))
#            ifst = self.ifSt(condExpr, [ self._tetr('ERROR', None, None, None) ])
            if not tovar:
                tovar = self._vlist.getTempVar(subtype)
            getst = [ self._tetr('SUBS', var[1], expr[1], tovar) ]
            code = code + ifst + getst
        else:
            print 'TO:', var[1].getType().tostr(), tovar
            if not tovar:
                subargs = []
                for a in var[1].getType().args:
                    subvarexpr = self.getSubVar(([], var[1]), a.getView())
                    _, subelem = self.getSubArray(subvarexpr, expr)
                    subargs.append(Var(a.getView(), subelem.getType(), ''))

                name = self._vlist.getUniqueName()
                temptypename = self._getTempTypeName()
                self._tlist[temptypename] = Type(temptypename, subargs)
                tovarcode, tovar = self.addVar('', name, self._tlist[temptypename])
                code = code + tovarcode

            for a in var[1].getType().args:
                tosubvar = self.getSubVar(([], tovar), a.getView())
                fromsubvar = self.getSubVar(([], var[1]), a.getView())
                code = code + self.getSubArray(fromsubvar, expr, tosubvar[1])[0]

        return code, tovar

    def setArraySize(self, var, num):
        num = int(num)
        newtype = 'array:%s:%s' % (num, var[1].type)
        var[1].type = deepcopy(var[1].type)
        if type(var[1].type) != type(''):
            for i, a in enumerate(var[1].type.args):
                subvarname = '__%s__%s' % (i, var[1].getView())
                subvarexpr = self.getVar(subvarname)
                var[1].type.args[i] = self.setArraySize(subvarexpr, num)[1]
                var[1].type.args[i].name = a.name
            var[1].type.name = newtype
        else:
            var[1].type = newtype
        self._vlist.setType(var[1].getView(), var[1].type)
        return var
            
